Passed
Push — master ( c4d61c...59bf76 )
by Kolja
01:06
created

jgfGraph.js ➔ isPartial   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
dl 0
loc 3
rs 10
nop 0
1
const check = require('check-types');
2
const _ = require('lodash');
3
const { cloneObject } = require('./common');
4
5
/**
6
 * A single JGF graph instance, always contained in a parent JGFContainer
7
 */
8
class JGFGraph {
9
10
    /**
11
     * Constructor
12
     * @param {*} type graph classification
13
     * @param {*} label a text display for the graph
14
     * @param {*} directed true for a directed graph, false for an undirected graph
15
     * @param {*} metadata about the graph
16
     */
17
    constructor(type = '', label = '', directed = true, metadata = null) {
18
        this._nodes = {};
19
        this._edges = [];
20
21
        this._type = type;
22
        this._label = label;
23
        this._directed = directed;
24
        this._metadata = metadata;
25
        this._isPartial = false;
26
    }
27
28
29
    /**
30
     * Loads the graph from a JGF JSON object
31
     * @param {*} graphJson JGF JSON object
32
     */
33
    loadFromJSON(graphJson) {
34
        this._type = graphJson.type;
35
        this._label = graphJson.label;
36
        // todo: this makes the graph always directed (even if false is passed), I doubt that this was the intention here
37
        this._directed = graphJson.directed || true;
38
        this._metadata = graphJson.metadata;
39
40
        this._nodes = {};
41
        this._edges = [];
42
        this.addNodes(graphJson.nodes);
43
        this.addEdges(graphJson.edges);
44
    }
45
46
    /**
47
     * Returns the isPartial flag
48
     */
49
    get isPartial() {
50
        return this._isPartial;
51
    }
52
53
    /**
54
     * Set the _isPartial flag
55
     */
56
    set isPartial(value) {
57
        this._isPartial = value;
58
    }
59
60
    /**
61
     * Returns the graph type
62
     */
63
    get type() {
64
        return this._type;
65
    }
66
67
    /**
68
     * Set the graph type
69
     */
70
    set type(value) {
71
        this._type = value;
72
    }
73
74
    /**
75
     * Returns the graph label
76
     */
77
    get label() {
78
        return this._label;
79
    }
80
81
    /**
82
     * Set the graph label
83
     */
84
    set label(value) {
85
        this._label = value;
86
    }
87
88
    /**
89
     * Returns the graph meta data
90
     */
91
    get metadata() {
92
        return this._metadata;
93
    }
94
95
    /**
96
     * Set the graph meta data
97
     */
98
    set metadata(value) {
99
        this._metadata = value;
100
    }
101
102
    /**
103
     * Returns all nodes
104
     */
105
    get nodes() {
106
        return cloneObject(Object.values(this._nodes));
107
    }
108
109
    /**
110
     * Returns all edges
111
     */
112
    get edges() {
113
        return cloneObject(this._edges);
114
    }
115
116
117
    /**
118
     * Returns the graph as JGF Json
119
     */
120
    get json() {
121
        let json = {
122
            type: this._type,
123
            label: this._label,
124
            directed: this._directed,
125
            nodes: this._getJsonNodes(),
126
            edges: this._getJsonEdges(),
127
        };
128
129
        let metadata = this._getJsonMetadata();
130
        if (metadata) {
131
            json.metadata = metadata;
132
        }
133
134
        return cloneObject(json);
135
    }
136
137
    _getJsonMetadata() {
138
        let metadata = null;
139
        if (check.assigned(this._metadata)) {
140
            metadata = this._metadata;
141
142
            if (this.isPartial) {
143
                metadata.isPartial = true;
144
            }
145
        }
146
147
        return metadata;
148
    }
149
150
    _getJsonNodes() {
151
        let nodes = [];
152
        if (check.assigned(this._nodes) && Object.keys(this._nodes).length > 0) {
153
            nodes = Object.values(this._nodes);
154
        }
155
156
        return nodes;
157
    }
158
159
    _getJsonEdges() {
160
        let edges = [];
161
        if (check.assigned(this._edges) && this._edges.length > 0) {
162
            edges = this._edges;
163
        }
164
165
        return edges;
166
    }
167
168
    /**
169
     * Adds a new node
170
     * @param {*} id Node id
171
     * @param {*} label Node label
172
     * @param {*} metadata about the node
173
     */
174
    addNode(id, label, metadata = null) {
175
        if (id in this._nodes) {
176
            throw new Error(`A node already exists with id = ${id}`);
177
        }
178
179
        let newNode = {
180
            id,
181
            label,
182
        };
183
184
        if (check.assigned(metadata)) {
185
            newNode.metadata = metadata;
186
        }
187
188
        this._nodes[newNode.id] = newNode;
189
    }
190
191
192
    /**
193
     * Adds multiple nodes
194
     * @param {*} nodes A collection of JGF node objects
195
     */
196
    addNodes(nodes) {
197
        for (let { id, label, metadata } of nodes) {
198
            this.addNode(id, label, metadata);
199
        }
200
    }
201
202
203
    /**
204
     * Updates an existing node
205
     * @param {*} nodeId Node id
206
     * @param {*} label Updated node label
207
     * @param {*} metadata Updated node meta data
208
     */
209
    updateNode(nodeId, label, metadata = null) {
210
        if (!(nodeId in this._nodes)) {
211
            throw new Error(`Can't update node. A node doesn't exist with id = ${nodeId}`);
212
        }
213
214
        let node = this._nodes[nodeId];
215
216
        if (check.assigned(label)) {
217
            node.label = label;
218
        }
219
220
        if (check.assigned(metadata)) {
221
            node.metadata = metadata;
222
        }
223
    }
224
225
226
    /**
227
     * Removes an existing graph node
228
     * @param {*} nodeId Node unique id
229
     */
230
    removeNode(nodeId) {
231
        if (!(nodeId in this._nodes)) {
232
            throw new Error(`A node doesn't exist with id = ${nodeId}`);
233
        }
234
235
        Reflect.deleteProperty(this._nodes, nodeId);
236
    }
237
238
    /**
239
     * Lookup a node by a node id
240
     * @param {*} nodeId Unique node id
241
     */
242
    getNode(nodeId) {
243
        if (!(nodeId in this._nodes)) {
244
            throw new Error(`A node doesn't exist with id = ${nodeId}`);
245
        }
246
247
        return cloneObject(this._nodes[nodeId]);
248
    }
249
250
    /**
251
     * Adds an edge between a source node and a target node
252
     * @param {*} source Source node id
253
     * @param {*} target Target node id
254
     * @param {*} relation Edge relation (AKA 'relationship type')
255
     * @param {*} label Edge label (the display name of the edge)
256
     * @param {*} metadata Custom edge meta data
257
     * @param {*} directed true for a directed edge, false for undirected
258
     */
259
    addEdge(source, target, relation = null, label = null, metadata = null, directed = null) {
260
        JGFGraph._guardAgainstEmptyNodeParams(source, target);
261
        this._guardNonPartialGraphsAgainstNonExistentNodes(source, target);
262
263
        let edge = {
264
            source,
265
            target,
266
        };
267
        if (check.assigned(relation)) {
268
            edge.relation = relation;
269
        }
270
        if (check.assigned(label)) {
271
            edge.label = label;
272
        }
273
        if (check.assigned(metadata)) {
274
            edge.metadata = metadata;
275
        }
276
        if (check.assigned(directed)) {
277
            edge.directed = directed;
278
        }
279
280
        this._edges.push(edge);
281
    }
282
283
    _guardNonPartialGraphsAgainstNonExistentNodes(source, target) {
284
        if (!this.isPartial) {
285
            this._guardAgainstNonExistentNodes(source, target);
286
        }
287
    }
288
289
    _guardAgainstNonExistentNodes(source, target) {
290
        if (!(source in this._nodes)) {
291
            throw new Error(`addEdge failed: source node isn't found in nodes. source = ${source}`);
292
        }
293
294
        if (!(target in this._nodes)) {
295
            throw new Error(`addEdge failed: target node isn't found in nodes. target = ${target}`);
296
        }
297
    }
298
299
    static _guardAgainstEmptyNodeParams(source, target) {
300
        if (!source) {
301
            throw new Error('addEdge failed: source parameter is not valid');
302
        }
303
304
        if (!target) {
305
            throw new Error('addEdge failed: target parameter is not valid');
306
        }
307
    }
308
309
    /**
310
     * Adds multiple edges
311
     * @param {*} edges A collection of JGF edge obejcts
312
     */
313
    addEdges(edges) {
314
        if (edges) {
315
            for (let edge of edges) {
316
                this.addEdge(edge.source, edge.target, edge.relation, edge.label, edge.metadata, edge.directed);
317
            }
318
        }
319
    }
320
321
    /**
322
     * Checks whether the passed edge is equal to an edge with all other three passed params.
323
     * @param {*} edge
324
     * @param {*} source Source node id
325
     * @param {*} target Target node id
326
     * @param {*} relation Specific edge relation type to remove. If empty then all edges will be removed, regardless of their relation
327
     */
328
    static _isEdgeEqual(edge, source, target, relation) {
329
        return edge.source === source &&
330
            edge.target === target &&
331
            (relation === '' || edge.relation === relation);
332
    }
333
334
    /**
335
     * Removes existing graph edges
336
     * @param {*} source Source node id
337
     * @param {*} target Target node id
338
     * @param {*} relation Specific edge relation type to remove. If empty then all edges will be removed, regardless of their relation
339
     */
340
    removeEdges(source, target, relation = '') {
341
        _.remove(this._edges, (edge) => JGFGraph._isEdgeEqual(edge, source, target, relation));
342
    }
343
344
    /**
345
     * Get edges between source node and target node, with an optional edge relation
346
     * @param {*} source
347
     * @param {*} target
348
     * @param {*} relation
349
     */
350
    getEdges(source, target, relation = '') {
351
        this._guardNonPartialGraphsAgainstNonExistentNodes(source, target);
352
353
        let edges = _.filter(this._edges, (edge) => JGFGraph._isEdgeEqual(edge, source, target, relation));
354
355
        return cloneObject(edges);
356
    }
357
358
    get graphDimensions() {
359
        let dimensions = {
360
            nodes: 0,
361
            edges: 0,
362
        };
363
364
        dimensions.nodes = Object.keys(this._nodes).length;
365
        dimensions.edges = this._edges.length;
366
367
        return dimensions;
368
    }
369
}
370
371
module.exports = {
372
    JGFGraph,
373
};